/*
 * Copyright (c) 2015-2016, Texas Instruments Incorporated
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * *  Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * *  Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/***** Includes *****/

#include <xdc/std.h>
#include <xdc/runtime/System.h>

#include <ti/sysbios/BIOS.h>
#include <ti/sysbios/knl/Task.h>
#include <ti/sysbios/knl/Semaphore.h>
#include <ti/sysbios/knl/Event.h>
#include <ti/sysbios/knl/Clock.h>

#include <ti/drivers/PIN.h>
#include <ti/drivers/Power.h>
#include <ti/drivers/power/PowerCC26XX.h>

#include <ti/drivers/Watchdog.h>

#ifdef DEVICE_FAMILY
    #undef DEVICE_FAMILY_PATH
    #define DEVICE_FAMILY_PATH(x) <ti/devices/DEVICE_FAMILY/x>
    #include DEVICE_FAMILY_PATH(driverlib/aon_batmon.h)
    #include DEVICE_FAMILY_PATH(driverlib/trng.h)
    //for reset as there is no driverlib reset function
    #include DEVICE_FAMILY_PATH(inc/hw_aon_sysctl.h)
#else
    #error "You must define DEVICE_FAMILY at the project level as one of cc26x0, cc26x0r2, cc13x0, etc."
#endif

/* Board Header files */
#include "Board.h"

#include "wsnprotocol/WsnProtocol.h"
#include "SceAdc.h"
#include "NodeTask.h"
#include "oad/oad.h"
#include "bim/ext_flash_layout.h"


/***** Defines *****/
#define NODE_TASK_STACK_SIZE 1024
#define NODE_TASK_PRIORITY   2

#define NODE_EVENT_ALL                  0xFFFFFFFF
#define NODE_EVENT_NEW_ADC_VALUE    (uint32_t)(1 << 0)
#define NODE_EVENT_OAD_NEXT_BLOCK    (uint32_t)(1 << 1)

/* A change mask of 0xFF0 means that changes in the lower 4 bits does not trigger a wakeup. */
#define NODE_ADCTASK_CHANGE_MASK                    0xFF0

/* Minimum slow Report interval is 50s (in units of samplingTime)*/
#define NODE_ADCTASK_REPORTINTERVAL_SLOW                50
/* Minimum fast Report interval is 1s (in units of samplingTime) for 30s*/
#define NODE_ADCTASK_REPORTINTERVAL_FAST                1
#define NODE_ADCTASK_REPORTINTERVAL_FAST_DURIATION_MS   30000

#define NODE_ACTIVITY_LED Board_LED0
#define NODE_ERROR_LED Board_LED1

//#define STESS_TEST

/***** Variable declarations *****/
static Task_Params nodeTaskParams;
Task_Struct nodeTask;    /* not static so you can see in ROV */
static uint8_t nodeTaskStack[NODE_TASK_STACK_SIZE];
Event_Struct nodeEvent;  /* not static so you can see in ROV */
static Event_Handle nodeEventHandle;
static uint16_t latestAdcValue;

/* Clock for the fast report timeout */
Clock_Struct fastReportTimeoutClock;     /* not static so you can see in ROV */
static Clock_Handle fastReportTimeoutClockHandle;

/* Semaphore for radio access */
Semaphore_Struct radioInitSem;  /* not static so you can see in ROV */
static Semaphore_Handle radioInitHandle;

/* Pin driver handle */
static PIN_Handle buttonPinHandle;
/*static*/ PIN_Handle ledPinHandle;
static PIN_State buttonPinState;
static PIN_State ledPinState;

static uint32_t tickCount;

static uint16_t oadBlockNum = 0;

/*
 * Radio Protocol callbacks for packet processing
 */
static void msgAck(uint8_t srcAddr);
static void fwVersionReqCb(uint8_t srcAddr);
static void oadImgIdentifyReqCb(uint8_t srcAddr, uint8_t *imgMetaData);
static void oadBlockRspCb(uint8_t srcAddr, uint8_t *blkData);
static void errorCb(uint8_t dstAddr, uint8_t expectedRsp, WSNProtocol_Status_t status);

WSNProtocol_packetCBs_t wsnProtocolCbs = {
    msgAck,                  //Incoming Ack
    NULL,                //Incming Adc packet
    NULL,                //Incoming Dual Mode Adc Packet
    fwVersionReqCb,    //Incoming FW Req
    NULL,                //Incoming FW Version Rsp
    oadImgIdentifyReqCb, //Incming Image Identify Req
    NULL,                //Incming Image Identify Rsp
    NULL,                //Incoming OAD Block Req
    oadBlockRspCb,       //Incoming OAD Block Rsp
    errorCb,            //Error callback
};

/*
 * OAD callbacks for processing OAD events
 */
static void getNextBlockReqCb(uint16_t blkNum);
static void rejectImageCb(img_hdr_t *pImgHdr);
static void sendStatusCb(uint8_t status);
oadCBs_t oadCbs = {
    getNextBlockReqCb,
    rejectImageCb,
    sendStatusCb,
};

/* Enable the 3.3V power domain used by the LCD */
PIN_Config pinTable[] = {
    NODE_ACTIVITY_LED | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX,
    NODE_ERROR_LED | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX,
    PIN_TERMINATE
};

/*
 * Application button pin configuration table:
 *   - Buttons interrupts are configured to trigger on falling edge.
 */
PIN_Config buttonPinTable[] = {
    Board_BUTTON0  | PIN_INPUT_EN | PIN_PULLUP | PIN_IRQ_NEGEDGE,
    PIN_TERMINATE
};

uint8_t dbg_flagSendingDm = 0;

static char nodeFwVersion[WSNPROTOCOL_FW_VERSION_STR_LEN] = "rfWsnNode v03.01.00\0";

#ifdef STESS_TEST
/* Clock for the fast report timeout */
Clock_Struct testClock;     /* not static so you can see in ROV */
static Clock_Handle testClockHandle;
#endif

/***** Prototypes *****/
static void nodeTaskFunction(UArg arg0, UArg arg1);
static void fastReportTimeoutCallback(UArg arg0);
static void adcCallback(uint16_t adcValue);
static void buttonCallback(PIN_Handle handle, PIN_Id pinId);
static void sendAdcData(uint16_t data);

/***** Function definitions *****/
void NodeTask_init(void)
{

    /* Create event used internally for state changes */
    Event_Params eventParam;
    Event_Params_init(&eventParam);
    Event_construct(&nodeEvent, &eventParam);
    nodeEventHandle = Event_handle(&nodeEvent);

    /* Create semaphore used for blocking radio access requests */
    Semaphore_Params semParam;
    Semaphore_Params_init(&semParam);
    Semaphore_construct(&radioInitSem, 0, &semParam);
    radioInitHandle = Semaphore_handle(&radioInitSem);

    /* Create clock object which is used for fast report timeout */
    Clock_Params clkParams;
    clkParams.period = 0;
    clkParams.startFlag = FALSE;
    Clock_construct(&fastReportTimeoutClock, fastReportTimeoutCallback, 1, &clkParams);
    fastReportTimeoutClockHandle = Clock_handle(&fastReportTimeoutClock);

#ifdef STESS_TEST
    /* setup test clock to send ADV event every 200ms*/
    clkParams.period = 200 * 1000 / Clock_tickPeriod;
    clkParams.startFlag = TRUE;
    Clock_construct(&testClock, adcCallback, 1, &clkParams);
    testClockHandle = Clock_handle(&testClock);
#endif

    /* Create the node task */
    Task_Params_init(&nodeTaskParams);
    nodeTaskParams.stackSize = NODE_TASK_STACK_SIZE;
    nodeTaskParams.priority = NODE_TASK_PRIORITY;
    nodeTaskParams.stack = &nodeTaskStack;
    Task_construct(&nodeTask, nodeTaskFunction, &nodeTaskParams, NULL);

    /* initialise the wsn protocol task */
    WSNProtocol_init();
}

static void nodeTaskFunction(UArg arg0, UArg arg1)
{
    /* Set Default parameters structure */
    static WSNProtocol_Params_t WSNProtocol_params;
    uint8_t nodeAddress;

    /* Use the True Random Number Generator to generate sensor node address randomly */;
    Power_setDependency(PowerCC26XX_PERIPH_TRNG);
    TRNGEnable();
    /* Do not accept the same address as the concentrator, in that case get a new random value */
    do
    {
        while (!(TRNGStatusGet() & TRNG_NUMBER_READY))
        {
            //wiat for randum number generator
        }
        nodeAddress = (uint8_t)TRNGNumberGet(TRNG_LOW_WORD);
    } while (nodeAddress == WSNPROTOCOL_CONCENTRATOR_ADDRESS);
    TRNGDisable();
    Power_releaseDependency(PowerCC26XX_PERIPH_TRNG);

    /* initialise and open the Wsn Protocol Task */
    WSNProtocol_Params_init(&WSNProtocol_params);
    WSNProtocol_params.devType = WSNProtocol_DevType_Node;
    WSNProtocol_params.devAddress = nodeAddress;
    WSNProtocol_params.pCallbacks = &wsnProtocolCbs;
    WSNProtocol_open(&WSNProtocol_params);

    /* Open LED pins */
    ledPinHandle = PIN_open(&ledPinState, pinTable);
    if (!ledPinHandle)
    {
        System_abort("Error initializing board 3.3V domain pins\n");
    }

    /* Start the SCE ADC task with 1s sample period and reacting to change in ADC value. */
    SceAdc_init(0x00010000, NODE_ADCTASK_REPORTINTERVAL_FAST, NODE_ADCTASK_CHANGE_MASK);
    SceAdc_registerAdcCallback(adcCallback);
    SceAdc_start();

    /* setup timeout for fast report timeout */
    Clock_setTimeout(fastReportTimeoutClockHandle,
            NODE_ADCTASK_REPORTINTERVAL_FAST_DURIATION_MS * 1000 / Clock_tickPeriod);

    /* start fast report and timeout */
    Clock_start(fastReportTimeoutClockHandle);

    buttonPinHandle = PIN_open(&buttonPinState, buttonPinTable);
    if (!buttonPinHandle)
    {
        System_abort("Error initializing button pins\n");
    }

    /* Setup callback for button pins */
    if (PIN_registerIntCb(buttonPinHandle, &buttonCallback) != 0)
    {
        System_abort("Error registering button callback function");
    }

    tickCount = Clock_getTicks();

    while(1) {
        /* Wait for event */
        uint32_t events = Event_pend(nodeEventHandle, 0, NODE_EVENT_ALL, BIOS_WAIT_FOREVER);

        /* If new ADC value, send this data */
        if (events & NODE_EVENT_NEW_ADC_VALUE) {
            /* Toggle activity LED */
            PIN_setOutputValue(ledPinHandle, NODE_ACTIVITY_LED,!PIN_getOutputValue(NODE_ACTIVITY_LED));

            dbg_flagSendingDm = 1;
            /* Send ADC value to concentrator */
            sendAdcData(latestAdcValue);

            /* Block on Ack */
            Semaphore_pend(radioInitHandle, BIOS_WAIT_FOREVER);
            //TODO: add timeout incase now response

            dbg_flagSendingDm = 0;
        }

        if(events & NODE_EVENT_OAD_NEXT_BLOCK)
        {
            if(oadBlockNum == 0x80)
            {
                dbg_flagSendingDm = 0;
            }

            WSNProtocol_sendOadImgBlockReq(WSNPROTOCOL_CONCENTRATOR_ADDRESS, oadBlockNum);

            /* Block on Rsp */
            Semaphore_pend(radioInitHandle, BIOS_WAIT_FOREVER);
            //TODO: add timeout incase now response
        }
    }
}

static void sendAdcData(uint16_t data)
{
    WSNProtocol_DmSensorData_t dmSensorData = {0};
    uint32_t currentTicks, prevTicks;

    prevTicks = tickCount;
    currentTicks = Clock_getTicks();

    //check for wrap around
    if (currentTicks > prevTicks)
    {
        //calculate time since last reading in 0.1s units
        dmSensorData.time100MiliSec += ((currentTicks - prevTicks) * Clock_tickPeriod) / 100000;
    }
    else
    {
        //calculate time since last reading in 0.1s units
        dmSensorData.time100MiliSec += ((prevTicks - currentTicks) * Clock_tickPeriod) / 100000;
    }
    tickCount = currentTicks;

    dmSensorData.batt = AONBatMonBatteryVoltageGet();
    dmSensorData.adcValue = data;
    dmSensorData.button = !PIN_getInputValue(Board_BUTTON0);

    WSNProtocol_sendDmPacket(WSNPROTOCOL_CONCENTRATOR_ADDRESS, dmSensorData);
}

/* Radio Protocol Callbacks */
static void msgAck(uint8_t srcAddr)
{
    //Allow app task to access radio
    Semaphore_post(radioInitHandle);
}

static void fwVersionReqCb(uint8_t srcAddr)
{
    WSNProtocol_sendFwVersionRsp(srcAddr, nodeFwVersion);
}

static void oadImgIdentifyReqCb(uint8_t srcAddr, uint8_t *imgMetaData)
{
    img_hdr_t *pImgHdr;
    uint8_t imageAccepted;

    pImgHdr = (img_hdr_t *)(imgMetaData + 4);

    /* remap the remote image to an application image */
    pImgHdr->res[2] = EFL_OAD_IMG_TYPE_APP;

    OAD_init(&oadCbs);

    /* process request */
    imageAccepted = OAD_imgIdentifyWrite(imgMetaData);

    WSNProtocol_sendOadIdentifyImgRsp(WSNPROTOCOL_CONCENTRATOR_ADDRESS, imageAccepted);
}

static void oadBlockRspCb(uint8_t srcAddr, uint8_t *blkData)
{
    //TODO: Add block number to API and check it is correct block
    OAD_imgBlockWrite(blkData);

    //Allow app task to access radio
    Semaphore_post(radioInitHandle);
}

static void errorCb(uint8_t dstAddr, uint8_t expectedRsp, WSNProtocol_Status_t status)
{
    //If it was a OAD Black Request that times out re-send the req
	if (expectedRsp == WSNPROTOCOL_PACKET_TYPE_OAD_BLOCK_RSP)
	{
		Event_post(nodeEventHandle, NODE_EVENT_OAD_NEXT_BLOCK);
	}

	PIN_setOutputValue(ledPinHandle, NODE_ERROR_LED,!PIN_getOutputValue(NODE_ERROR_LED));

    //Allow app task to access radio
    Semaphore_post(radioInitHandle);
}

/* OAD Callbacks */
static void getNextBlockReqCb(uint16_t blockNum)
{
    oadBlockNum = blockNum;
    Event_post(nodeEventHandle, NODE_EVENT_OAD_NEXT_BLOCK);
}

static void rejectImageCb(img_hdr_t *pImgHdr)
{

}

static void sendStatusCb(uint8_t status)
{
    // Close the OAD target if it is open
    OADTarget_close();

    if(status == OAD_SUCCESS)
    {
        Watchdog_Params params;
        Watchdog_Handle watchdog;

        PIN_setOutputValue(ledPinHandle, NODE_ACTIVITY_LED,1);

        Watchdog_init();

        Watchdog_Params_init(&params);
        params.resetMode = Watchdog_RESET_ON;

        watchdog = Watchdog_open(Board_WATCHDOG0, &params);

        Watchdog_setReload(watchdog, 0);

        // Enable reset interrupt.
        CPUcpsie();

        //reduced code size version of reboot
        //(HWREG( AON_SYSCTL_BASE + AON_SYSCTL_O_RESETCTL ) |= AON_SYSCTL_RESETCTL_SYSRESET_M);
        //*((uint32_t *)0x40082270) = 1;

        // Stay here until reset.
        for (;;);
    }
}

void adcCallback(uint16_t adcValue)
{
    /* Save latest value */
    latestAdcValue = adcValue;

    /* Post event */
    Event_post(nodeEventHandle, NODE_EVENT_NEW_ADC_VALUE);
}

/*
 *  ======== buttonCallback ========
 *  Pin interrupt Callback function board buttons configured in the pinTable.
 */
void buttonCallback(PIN_Handle handle, PIN_Id pinId)
{
    /* Debounce logic, only toggle if the button is still pushed (low) */
    CPUdelay(8000*50);


    if (PIN_getInputValue(Board_BUTTON0) == 0)
    {
        //start fast report and timeout
        SceAdc_setReportInterval(NODE_ADCTASK_REPORTINTERVAL_FAST, NODE_ADCTASK_CHANGE_MASK);
        Clock_start(fastReportTimeoutClockHandle);
    }
}

void fastReportTimeoutCallback(UArg arg0)
{
    //stop fast report
    SceAdc_setReportInterval(NODE_ADCTASK_REPORTINTERVAL_SLOW, NODE_ADCTASK_CHANGE_MASK);
}
